#include <iostream>
#include <string>
#include <vector>
#include <utility>
#include <cstdlib>
#include <memory>
#include "connectionclosedexception.h"
#include "connection.h"
#include "client_parser.h"
#include "protocol.h"

using namespace std;
const string fail_message = "Bad reply from server, please try again";

ClientParser::ClientParser() {}

string ClientParser::handle_input(const shared_ptr<Connection>& conn) {
	unsigned char command = conn->read();
	string output;
	switch (command) {
	
	case Protocol::ANS_LIST_NG:
		{
			output = "Existing newsgroups:\n";
			unsigned int nbr_of_rows = capture_int(conn);
			for(unsigned int i = 0; i != nbr_of_rows; ++i) {
				unsigned int id = capture_int(conn);
				string row = capture_string(conn);
				output.append(to_string(id)).append(", ").append(row).append("\n");
			}
		}
		break;
	
	case Protocol::ANS_CREATE_NG:
		{
			unsigned char next = conn->read();
			if(next == Protocol::ANS_ACK) {
				output = "Creation of newsgroup succeeded!";
			} else if(next == Protocol::ANS_NAK) {
				next = conn->read();
				if(next == Protocol::ERR_NG_ALREADY_EXISTS) {
					output = "Creation of newsgroup failed, newsgroup already exists!";
				}
			}
		}
		break;

	case Protocol::ANS_DELETE_NG:
		{
			unsigned char next = conn->read();
			if(next == Protocol::ANS_ACK) {
				output = "Deletion of newsgroup succeeded!";
			} else if(next == Protocol::ANS_NAK) {
				next = conn->read();
				if(next == Protocol::ERR_NG_DOES_NOT_EXIST) {
					output = "Deletion of newsgroup failed, newsgroup does not exist!";
				}
			}
		}
		break;

	case Protocol::ANS_LIST_ART:
		{
			unsigned char next = conn->read();
			if(next == Protocol::ANS_ACK) {
				output = "Articles in newsgroup:\n";
				unsigned int nbr_of_articles = capture_int(conn);
				for(unsigned int i = 0; i != nbr_of_articles; ++i) {
					unsigned int id = capture_int(conn);
					string title = capture_string(conn);
					output.append(to_string(id)).append(", ").append(title).append("\n");
				}
			} else if(next == Protocol::ANS_NAK) {
				next = conn->read();
				if(next == Protocol::ERR_NG_DOES_NOT_EXIST) {
					output = "Listing failed, newsgroup does not exist!";
				}
			}
		}
		break;	

	case Protocol::ANS_CREATE_ART:
		{
			unsigned char next = conn->read();
			if(next == Protocol::ANS_ACK) {
				output = "Creation of article succeeded!";
			} else if(next == Protocol::ANS_NAK) {
				next = conn->read();
				if(next == Protocol::ERR_NG_DOES_NOT_EXIST) {
					output = "Creation of article failed, newsgroup does not exist!";
				}
			}
		}
		break;

	case Protocol::ANS_DELETE_ART:
		{
			unsigned char next = conn->read();
			if(next == Protocol::ANS_ACK) {
				output = "Deletion of article succeeded!";
			} else if(next == Protocol::ANS_NAK) {
				next = conn->read();
				if(next == Protocol::ERR_NG_DOES_NOT_EXIST) {
					output = "Deletion of article failed, newsgroup does not exist!";
				} else if(next == Protocol::ERR_ART_DOES_NOT_EXIST) {
					output = "Deletion of article failed, article does not exist!";
				}
			}
		}	
		break;

	case Protocol::ANS_GET_ART:
		{
			unsigned char next = conn->read();
			if(next == Protocol::ANS_ACK) {
				string title = capture_string(conn);
				string author = capture_string(conn);
				string text = capture_string(conn);
				output.append("Title: ").append(title).append("\n").append("Author: ").append(author).append("\n").append("Text: ").append(text);
			} else if(next == Protocol::ANS_NAK) {
				next = conn->read();
				if(next == Protocol::ERR_NG_DOES_NOT_EXIST) {
					output = "Getting article failed, newsgroup does not exist!";
				} else if(next == Protocol::ERR_ART_DOES_NOT_EXIST) {
					output = "Getting article failed, article does not exist!";
				}
			}
		}
		break;
	default:
		throw invalid_argument(fail_message);
		break;
	}
	command = conn->read(); //Catch the end byte
	if(command != Protocol::ANS_END) {
		throw invalid_argument(fail_message);
	}
	output += "\n";
	return output;
}

//Captures a string_p
string ClientParser::capture_string(const shared_ptr<Connection>& conn) {
	unsigned char string_p = conn->read();
	if(string_p != Protocol::PAR_STRING) {
		throw invalid_argument(fail_message);
	}
	unsigned int len = parse_int(conn);
	string text = create_string(conn, len);
	return text;
}

//Creates a string (helper method for capture_string)
string ClientParser::create_string(const shared_ptr<Connection>& conn, unsigned int len) {
	string output;
	for(unsigned int i = 0; i != len; ++i) {
		output += conn->read();
	}
	return output;
}

//Captures an num_p
unsigned int ClientParser::capture_int(const shared_ptr<Connection>& conn) {
	unsigned char num_p = conn->read();
	if(num_p != Protocol::PAR_NUM) {
		throw invalid_argument(fail_message);
	}
	unsigned int N = parse_int(conn);
	return N;
}

//Creates an unsigned int (helper method for capture_int)
unsigned int ClientParser::parse_int(const shared_ptr<Connection>& conn) {
	unsigned char byte1 = conn->read();
	unsigned char byte2 = conn->read();
	unsigned char byte3 = conn->read();
	unsigned char byte4 = conn->read();
	return (byte1 << 24) | (byte2 << 16) | (byte3 << 8) | byte4;
}

//Splits an int and pushes it onto an output NOT including PAR_NUM flag
void ClientParser::push_int_no_flag(vector<unsigned char> &output, unsigned int input) {
	output.push_back(input >> 24);
	output.push_back(input >> 16);
	output.push_back(input >> 8);
	output.push_back(input);
}

//Splits an int and pushes it onto an output, following protocol num_p
void ClientParser::push_int(vector<unsigned char> &output, unsigned int input) {
	output.push_back(Protocol::PAR_NUM);
	push_int_no_flag(output, input);
}
 
//Pushes a string onto output vector, following protocol string_p
void ClientParser::push_string(vector<unsigned char> &output, string input) {
	output.push_back(Protocol::PAR_STRING);
	unsigned int len = input.size();
	push_int_no_flag(output,len);
	for(unsigned char c : input) {
		output.push_back(c);
	}
}

string ClientParser::translate(unsigned char c) {
	string reply;
	switch(c) {
	
	case(Protocol::COM_LIST_NG):
		reply = "COM_LIST_NG";
		break;
	case(Protocol::COM_CREATE_NG):
		reply = "COM_CREATE_NG";
		break;
	case(Protocol::COM_DELETE_NG):
		reply = "COM_DELETE_NG";
		break;
	case(Protocol::COM_LIST_ART):
		reply = "COM_LIST_ART";
		break;
	case(Protocol::COM_CREATE_ART):
		reply = "COM_CREATE_ART";
		break;
	case(Protocol::COM_DELETE_ART):
		reply = "COM_DELETE_ART";
		break;
	case(Protocol::COM_GET_ART):
		reply = "COM_GET_ART";
		break;
	case(Protocol::COM_END):
		reply = "COM_END";
		break;

	case(Protocol::ANS_LIST_NG):
		reply = "ANS_LIST_NG";
		break;
	case(Protocol::ANS_CREATE_NG):
		reply = "ANS_CREATE_NG";
		break;
	case(Protocol::ANS_DELETE_NG):
		reply = "ANS_DELETE_NG";
		break;
	case(Protocol::ANS_LIST_ART):
		reply = "ANS_LIST_ART";
		break;
	case(Protocol::ANS_CREATE_ART):
		reply = "ANS_CREATE_ART";
		break;
	case(Protocol::ANS_DELETE_ART):
		reply = "ANS_DELETE_ART";
		break;
	case(Protocol::ANS_GET_ART):
		reply = "ANS_GET_ART";
		break;
	case(Protocol::ANS_END):
		reply = "ANS_END";
		break;
	case(Protocol::ANS_ACK):
		reply = "ANS_ACK";
		break;
	case(Protocol::ANS_NAK):
		reply = "ANS_NAK";
		break;

	case(Protocol::PAR_STRING):
		reply = "PAR_STRING";
		break;
	case(Protocol::PAR_NUM):
		reply = "PAR_NUM";
		break;

	case(Protocol::ERR_NG_ALREADY_EXISTS):
		reply = "ERR_NG_ALREADY_EXISTS";
		break;
	case(Protocol::ERR_NG_DOES_NOT_EXIST):
		reply = "ERR_NG_DOES_NOT_EXIST";
		break;
	case(Protocol::ERR_ART_DOES_NOT_EXIST):
		reply = "ERR_ART_DOES_NOT_EXIST";
		break;
	default:
		reply = static_cast<unsigned int>(c);
		break;
	}
	return reply;
}





